package com.xiyuan;

import com.xiyuan.config.Config;
import com.xiyuan.config.Group;
import com.xiyuan.config.Item;
import com.xiyuan.smartutils.*;
import com.xiyuan.smartutils.constants.SysConstants;
import com.xiyuan.smartutils.filters.Filter;
import com.xiyuan.smartutils.filters.FilterEndsWith;
import com.xiyuan.smartutils.filters.FilterHandler;
import com.xiyuan.smartutils.logging.LogFactory;
import com.xiyuan.smartutils.logging.Logger;

import java.io.File;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;


/**
 * 系统工程入口类<br><br>
 * <p>
 * 一、判断是否是命令，如果是转到ZhiCommand处理
 * 二、启动工程，调用startup方法
 * 1.检查Java版本
 * 2.加载配置文件信息<br>
 * 3.注册监听<br>
 * 4.加载ClassPath和ClassLoader<br>
 * 5.加载工程服务<br>
 * 6.生成工程注册Shutdown锚
 *
 * @version v1.0.0 @author lgz 2017-8-27 新建与整理
 */
public final class SysBoot implements SysConstants {
    
    private static final Logger log = LogFactory.getLogger(SysBoot.class);
    
    /**
     * 不允许实例化
     */
    private SysBoot() {}
    
    /**
     * 工程启动入口
     */
    public static void main(String[] args) {
        if (args.length == 0) {
            // 无参数，开发环境启动
            startup(args);
        }
        else if (args.length == 1) {
            // 单参数
            if (START_A.equals(args[0])) {
                // 生产环境启动
                startup(args);
            }
            else {// 其他6种命令
                SysCommand.command(args);
            }
        }
        else {// 不支持的参数
            System.out.println("不支持的参数，请使用-h查看支持的参数");
        }
    }
    
    /**
     * 工程启动
     */
    private static void startup(String[] args) {
        // 1.检查版本
        if (!chkJavaVersion()) {
            System.exit(1);
            return;
        }
        
        // 2.加载配置文件
        if (!loadConfig()) {
            System.exit(1);
            return;
        }
        
        // 3.注册监听端口
        if (!registerMonitor()) {
            System.exit(1);
            return;
        }
        
        // 注册监听端口成功之后表示开始初始化
        log.info("系统[%s]-版本[%s]初始化开始...\r\n", Global.getName(), Global.getVersion());
        
        // 4.加载LIB到CLASSPATH
        if (!loadClasspath(args)) {
            System.exit(1);
            return;
        }
        
        // 5.加载类短别称
        if (!loadClassAliasName()) {
            System.exit(1);
            return;
        }
        
        // 6.创建服务
        if (!createService()) {
            System.exit(1);
            return;
        }
        
        // 7.注册工程关闭锚
        Runtime.getRuntime().addShutdownHook(new ShutdownHook());
        
        // 初始化完成打印日志
        log.info("系统[%s]-版本[%s]初始化完成!!!\r\n", Global.getName(), Global.getVersion());
    }
    
    /**
     * 检查JAVA版本
     */
    public static boolean chkJavaVersion() {
        String vers = Systems.getJavaVersion();
        if (vers.compareTo("1.7") < 0) {
            System.out.println("JDK版本不能低于[1.7]");
            System.out.println("java.vm.info:  " + Systems.getVmInfo());
            System.out.println("java.vm.name:  " + Systems.getVmName());
            System.out.println("java.vm.version:  " + Systems.getVmVersion());
            return false;
        }
        
        return true;
    }
    
    /**
     * 初始化配置文件
     */
    private static boolean loadConfig() {
        // 先检查xml再检查ini
        String path = null;
        if (Files.exists(BOOT_CONF_XML)) {path = BOOT_CONF_XML;}
        else if (Files.exists(BOOT_CONF_INI)) {path = BOOT_CONF_INI;}
        
        if (path == null) {
            log.error("配置文件[%s]和[%s]都不存在，请按手册配置目录结构", BOOT_CONF_XML, BOOT_CONF_INI);
            return false;
        }
        
        Config config = new Config(NAME, path);
        
        try {
            // 1.加载根配置文件
            config.load();
            
            // 1.1检查[boot]下name,version,port三个字段是否必须
            if (!config.hasGroup(BOOT) || !config.hasItem(BOOT, ITEM_NAME) || !config.hasItem(BOOT,
                                                                                              ITEM_VERSION) || !config.hasItem(
                    BOOT,
                    ITEM_PORT)) {
                log.error("配置文件[boot]组中name,version,port必须的，请配置好再启动");
                return false;
            }
            
            // 1.2把配置加到全局表中
            String result = Global.addConfig(config);
            if (result != null) {
                log.error("根配置文件[%s]在这里[%s]有配置错误", path, result);
                return false;
            }
            
        }
        catch (Exception e) {
            log.error("初始化配置文件[%s]时异常", e, path);
            return false;
        }
        
        // 2.判断是否需要加载其他的配置文件
        if (config.hasGroup(CONFIG)) {
            Collection<Item> itemList = config.getItemList(CONFIG);
            for (Item item : itemList) {// 2.1循环加载
                try {
                    Config conf = new Config(item.getKey(), item.getString()).load();
                    
                    // 2.2加到全局表中，失败退出
                    String result = Global.addConfig(conf);
                    if (result != null) {
                        log.error("配置文件[%s]存在和别的配置文件相同的配置组[%s]", item.getString(), result);
                        return false;
                    }
                }
                catch (Exception e) {
                    log.error("初始化配置文件[%s]时异常", e, item.getString());
                    return false;
                }
            }
        }
        
        return true;
    }
    
    /**
     * 初始化监听
     */
    private static boolean registerMonitor() {
        // 启动监视命令线程
        int port = Global.getPort();
        if (port < 1 || port > 65535) {
            log.error("配置文件[boot.port]不是正确的端口");
            return false;
        }
        
        SysMonitor monitor = new SysMonitor(port);
        return monitor.start();
    }
    
    /**
     * 加载LIB
     */
    private static boolean loadClasspath(String[] args) {
        if (args.length < 1) {// 1.在eclipse或Zhiqim Studio中不需要程序中加载lib，由studio加载
            // 2.在zhiqim.exe,zhiqim.lix,zhiqim.mac中会传入lib参数表示需要加载
            return true;
        }
        
        try {
            SysClasspath classpath = new SysClasspath();
            classpath.initClasspath(Global.getString(BOOT, ITEM_LIBEXT));
            Global.add(SysClasspath.class, classpath);
            return true;
        }
        catch (Exception e) {
            log.error("初始化类加载路径时异常", e);
            return false;
        }
    }
    
    /**
     * 创建服务
     */
    public static boolean createService() {
        if (!Global.hasGroup(SERVICE)) {return true;}
        
        try {
            Group group = Global.getGroup(SERVICE);
            for (Item item : group.list()) {
                String className = item.getString();
                Object obj = Global.newInstance(className);
                if (obj == null) {
                    log.error("服务[%s]未找到该类", className);
                    return false;
                }
                
                if (!(obj instanceof Service)) {
                    log.error("服务[%s]未实现Service接口，退出工程", className);
                    return false;
                }
                
                Service service = (Service) obj;
                service.setId(item.getKey());
                if (!service.create()) {
                    log.error("服务[%s]初始化失败，退出工程", className);
                    return false;
                }
                
                // 放置到全局变量中，以便业务使用
                Global.addService(service);
            }
            
            return true;
        }
        catch (Exception e) {
            log.error("初始化创建服务时异常，退出工程", e);
            return false;
        }
    }
    
    /**
     * 加载类短别称，在全类路径下进行include/exclude处理
     */
    private static boolean loadClassAliasName() {
        List<String> pathList = Lists.toList(Systems.getClassPaths());
        String include = Global.getString(BOOT, ITEM_INCLUDE);
        String exclude = Global.getString(BOOT, ITEM_EXCLUDE);
        
        if (Validates.isNotBlank(include)) {
            List<String> patternList = Lists.toStringList(Files.toLinuxPath(include));
            first:
            for (Iterator<String> it = pathList.iterator(); it.hasNext(); ) {
                String path = Files.toLinuxPath(it.next());
                for (String pattern : patternList) {
                    if (Validates.isMatch(path, pattern)) {
                        // 匹配一个即可
                        continue first;
                    }
                }
                
                // 未包含的删除
                it.remove();
            }
        }
        
        if (Validates.isNotBlank(exclude)) {
            List<String> patternList = Lists.toStringList(Files.toLinuxPath(exclude));
            first:
            for (Iterator<String> it = pathList.iterator(); it.hasNext(); ) {
                String path = Files.toLinuxPath(it.next());
                for (String pattern : patternList) {
                    if (Validates.isMatch(path, pattern)) {
                        // 匹配一个则排删
                        it.remove();
                        continue first;
                    }
                }
            }
        }
        
        // 最后去加载
        return loadClassAliasName(pathList);
    }
    
    /**
     * 加载类短别称
     */
    public static boolean loadClassAliasName(List<String> pathList) {
        Filter<String> filter = new FilterEndsWith(".class");
        FilterHandler handler = obj -> {
            Object[] objs = (Object[]) obj;
            if (objs[0] instanceof File) {
                File classPath = (File) objs[0];
                File classFile = (File) objs[1];
                String className = Files.getClassName(classPath, classFile);
                return setClassAlias(className);
            }
            else {
                JarEntry jarEntry = (JarEntry) objs[1];
                String className = Jars.getClassName(jarEntry);
                return setClassAlias(className);
            }
        };
        // Files.filter();
        return Resources.scanClassPath(filter, handler, pathList);
    }
    
    /**
     * 通过全称类，找到短名类注解，设置短名类名称和类结构到全局变量中
     *
     * @param className 类全称
     */
    private static boolean setClassAlias(String className) {
        Class<?> cls = Classes.forName(className);
        if (cls == null) {return true;}
        
        String alias = Annotations.getClassAlias(cls);
        if (alias == null) {return true;}
        
        Class<?> has = Global.getClass(alias);
        if (has != null) {
            log.error("[%s]和[%s]的简称[%s]不允许相同", className, has.getName(), alias);
            return false;
        }
        
        Global.addClass(alias, cls);
        return true;
    }
}
